#!/bin/sh
set -f

CR="
"

error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }

bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
read_uptime() {
	local idle
	{ read _RET idle </proc/uptime; } >/dev/null 2>&1 || _RET=""
}

time_call() {
	local start="$1" end="$2" ret="" delta="" t=""
	read_uptime
	start="${_RET%.*}${_RET#*.}"
	"$@"
	ret=$?
	read_uptime
	end="${_RET%.*}${_RET#*.}"
	delta=$(($end-$start))
	case $delta in
		?)  _RET_TIME=0.0$delta;;
		??) _RET_TIME=0.$delta;;
		*)
			t=${delta%??}
			_RET_TIME="$t.${delta#${t}}"
			;;
	esac
	return $ret
}


debug() {
	local level=${1}; shift;
	[ "${level}" -gt "${VERBOSITY:-0}" ] && return
	error "${@}"
}

net_get_gateway() {
	_RET=$(route -n | awk '$1 == "0.0.0.0" && $2 != "0.0.0.0" { print $2 }')
}
net_get_nameservers() {
	local gw nslist
	local t1 t2 t3 nslist="" ns=""
	while read t1 t2 t3; do
		case "$t1" in
			nameserver) nslist="${nslist} ${t2}";;
		esac
	done < /etc/resolv.conf
	_RET="$nslist"
}

check_ping_gateway() {
	local gw="$1"
	[ -z "$gw" ] && net_get_gateway && gw=$_RET
	[ -n "$gw" ] || { debug 2 "No gateway found"; return 1; }
	ping -c 1 $gw -W 3 >/dev/null 2>&1
}

debug_connection() {
	local gw=""

	echo "############ debug start ##############"
	echo "### /etc/init.d/sshd start"
	/etc/init.d/sshd start
	net_get_gateway && gw=$_RET
	echo "### ifconfig -a"
	ifconfig -a
	echo "### route -n"
	route -n
	echo "### cat /etc/resolv.conf"
	cat /etc/resolv.conf
	if [ -n "${gw}" ]; then
		echo "### ping -c 5 ${gw}"
		ping -c 5 ${gw}
	else
		echo "### gateway not found"
	fi
	local t1 t2 t3 nslist="" ns=""
	while read t1 t2 t3; do
		case "$t1" in
			nameserver) nslist="${nslist} ${t2}";;
		esac
	done < /etc/resolv.conf
	echo "### pinging nameservers"
	for ns in ${nslist}; do
		echo "#### ping -c 5 ${ns}"
		ping -c 5 ${ns}
	done
	echo "### uname -a"
	uname -a
	lxc-is-container || { echo "### lsmod"; lsmod; }
	echo "### dmesg | tail"
	dmesg | tail
	echo "### tail -n 25 /var/log/messages"
	tail -n 25 /var/log/messages
	echo "############ debug end   ##############"
}

dev_with_info() {
	local info="$1" dev=""
	dev=$(blkid -l -o device -t "$info") &&
		[ -b "$dev" ] && _RET="$dev"
	return
}

check_sbin_init() {
	# used as mount_callback callback
	debug 1 "check_sbin_init: $*"
	local dev="$1" mp="$2"
	shift 2
	[ -x "$mp/sbin/init" ]
}

search_for_blank() {
	# search_for_root(info, mountpoint)
	local info="$1" opts="$2" mp="$3"
	mount_callback "$info" "$opts" "$mp" false
}

search_for_root() {
	# search_for_root(info, mountpoint)
	local info="$1" opts="$2" mp="$3"
	shift 2;
	mount_callback "$info" "$opts" "$mp" cbfail check_sbin_init
}

mount_callback() {
	# mount_fs_callback(info, mount_opts, target, umount, callback [args])
	# attempt to mount a device with info 'info' with mount_opts
	# at target.  if target is 'tmp', then create a temp dir.
	# on success call callback
	# unmount:
	#   true: unmount
	#   cbfail: unmount callback returns non-zero
	#   false: do not unmount
	# callback is called like:
	#    callback <device> <mountpoint> [args]
	#
	# info can be:
	#   /*        : block device path
	#   LABEL=*   : filesystem with provided label
	#   UUID=*    : filesystem with provided uuid
	#
	# return value:
	#   2: unknown failure
	#   3: mount failed
	#   4: device not found
	# otherwise, return the callback's return code.
	local info="$1" mount_opts="$2" target="$3" unmount="${4-true}"
	local fail_unknown=2 fail_mount=3 fail_not_found=4
	local tmpd="" msg="" mp=""
	_RET_MCB_MP=""
	_RET_MCB_DEV=""
	shift 4;
	if [ $# -eq 0 ]; then
		set -- :
	fi
	local callback="$1"
	shift
	case "$unmount" in
		true|false|cbfail) :;;
		*)
			error "unexpected input for unmount: '$unmount'"
			return $fail_unknown
	esac
	case "$info" in
		/*) dev="$info"
			[ -b "$dev" ] || return $fail_not_found;;
		LABEL=*|UUID=*)
			dev_with_info "$info" && dev="$_RET" ||
				return $fail_not_found;;
		*)
			error "unexpected mount info: $info";
			return $fail_unknown;;
	esac
	if [ "$target" = "tmp" ]; then
		tmpd=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.mp.XXXXXX") ||
			return $fail_unknown
		debug 2 "mktemp failed"
		mp="$tmpd"
	else
		mp="$target"
	fi
	case "$mount_opts" in
		ro|rw) mount_opts="-o $mount_opts";;
	esac
	msg="mcb [info=$info dev=$dev target=$target unmount=$unmount callback=$callback]"
	debug 1 "$msg: mount '$dev' '$mount_opts' '$mp'"
	mount "$dev" $mount_opts "$mp" || {
		ret=$?;
		debug 2 "$msg: failed mount $dev";
		if [ -n "$tmpd" ]; then
			rmdir "$tmpd"
			return $fail_mount;
		fi
	}
	"$callback" "$dev" "$mp" "$@"
	ret=$?
	msg="${msg%]} ret=$ret"
	if [ "$unmount" = "true" ] ||
		[ "$unmount" = "cbfail" -a "$ret" -ne 0 ]; then
		umount "$dev" || {
			error "$msg: failed to unmount"
			return $fail_unknown
		}
		if [ "$target" = "tmp" ]; then
			rmdir "$tmpd" || {
				error "$msg: failed rmdir $tmpd"
				return $fail_unknown
			}
		fi
	else
		[ "$unmount" = "cbfail" ] &&
			debug 2 "$msg: not unmounting cbfail=0"
		_RET_MCB_MP="$mp"
	fi
	_RET_MCB_DEV="$dev"
	return $ret
}

mcu_drop_dev_arg() {
	# mount_callback_umount called the callbacks with only
	# mountpoint, while mount_callback calls dev and mountpoint
	# this is used to provide legacy interface.
	local dev="$1" mp="$2" fn="$3"
	shift 3
	debug 1 "mcudda: fn=$fn dev=$dev mp=$mp : $*"
	"$fn" "$mp" "$@"
}

mount_callback_umount() {
	# this is legacy-ish wrapper around mount_callback
	local dev="$1" opts="$2"
	shift 2
	mount_callback "$dev" "$opts" tmp true mcu_drop_dev_arg "$@"
}

find_devs_with() {
	# return a list of devices that match filter
	# where filter is like:
	#  TYPE=<filesystem>
	#  LABEL=<label>
	#  UUID=<uuid>
	local filter="$1"
	local out rc ret=""
	out=$(blkid "-t$filter" "-odevice" 2>/dev/null)
	rc=$?
	if [ $rc -eq 0 ]; then
		local item=""
		for item in ${out}; do
			ret="${ret} ${item}"
		done
		_RET=${ret# }
		return 0
	elif [ $rc -eq 2 ]; then
		# 2 is "no matching devices"
		_RET=""
		return 0
	fi
	return $rc
}

ipinfo() {
	# return a list of ip info
	# each line contain: ifname,up/down,ipv4,ipv4mask
	local cur_if="" cur_up=0 cur_inargs=0 cur_good=0 cur_ipv4="" cur_ipv6_global="" cur_ipv6_local="" cur_nm
	local data="" dline=""
	while read line; do
		case "$line" in
			[0-9]:*|[0-9][0-9]:*|[0-9][0-9][0-9]:*)
				if [ -n "$cur_if" ]; then
					dline="${cur_if},${cur_up},${cur_ipv4},${cur_nm},${cur_ipv6_local},${cur_ipv6_global}"
					data="${data}|${dline}"
				fi
				set -- ${line}
				cur_if=${2%:}
				cur_up=down
				cur_ipv4=""
				cur_ipv6_global=""
				cur_ipv6_local=""
				case "$3" in
					*,UP,*|*,UP\>|\<UP,*) cur_up=up;;
				esac
		esac
		case "$line" in
			*inet\ *)
				set -- $line
				[ "${2#*/}" != "$2" ] && {
					cur_ipv4="${2%/*}";
					cur_nm="${2#*/}";
				}
				;;
			*inet6\ *) :
				# don't know how to do this
				set -- $line
				[ "$4" == "global" ] && cur_ipv6_global="$2"
				[ "$4" == "link" ] && cur_ipv6_local="$2";;
		esac
	done
	if [ -n "$cur_if" ]; then
		data="${data}|${cur_if},${cur_up},${cur_ipv4},${cur_nm},${cur_ipv6_local},${cur_ipv6_global}"
	fi
	_RET="${data#|}"
	return 0
}

# upon return, the following globals are set (KC=Kernel Commandline)
# KC_CONSOLES : full path (including /dev) to all console= entries that exist
# KC_CONSOLE : the last entry on the kernel command line
# KC_PREF_CONSOLE : the last existing entry on kernel command line
# KC_ROOT : root= variable
# KC_DEBUG_INITRAMFS : set to '1' if debug-initramfs is on commandline
# KC_VERBOSE: set to '1' if 'verbose' on commandline
# KC_RAMDISK_ROOT: set to 1 if cmdline said not to mount a root (0 otherwise)
# KC_INIT: init= variable
parse_cmdline() {
	# expects that /dev is mounted/populated
	KC_VERBOSE=0
	KC_INIT="/sbin/init"
	KC_RAMDISK_ROOT=0
	local cmdline="$1" tok="" val="" key="" consoles=""
	local last_con="" pref_con=""
	is_lxc && return 0
	KC_CONSOLE=/dev/tty0
	set -f
	[ $# -eq 0 ] && read cmdline < /proc/cmdline
	for tok in $cmdline; do
		val=${tok#*=}
		key=${tok%%=*}
		case $key in
			console)
				val="/dev/${val#/dev}"
				last_con="$val"
				[ -e "$val" ] && { echo "" > "$val"; } 2>/dev/null || continue
				consoles="$consoles $val"
				pref_con="$val"
				KC_CONSOLE="$val"
				;;
			rdroot) KC_RAMDISK_ROOT=1;;
			root) KC_ROOT="$val";;
			init) KC_INIT="$val";;
			debug-initramfs) KC_DEBUG_INITRAMFS=1;;
			verbose) KC_VERBOSE=1;;
			verbose=*) KC_VERBOSE=${tok#verbose=};;
		esac
	done

	case "$KC_ROOT" in
		[Nn][Oo][Nn][Ee]|ramdisk)
			KC_RAMDISK_ROOT=1;
			KC_ROOT="ramdisk";;
	esac

	# set KC_PREF_CONSOLE to the last writable console
	# if that is the same as the last
	[ "$pref_con" = "$last_con" ] && pref_con=""
	KC_PREF_CONSOLE="$pref_con"
	KC_CONSOLES="${consoles# }"

	set +f
	return
}

idebug() {
	[ "$KC_VERBOSE" = "0" ] && return
	echo "debug:" "$@"
}
iinfo() {
	[ "$KC_QUIET" = "1" ] && return
	echo "info:" "$@"
}
failure() {
	echo "FATAL: ====" "$@" "===="
	echo "Executing /bin/sh. maybe you can help"
	exec /bin/sh
}

is_mounted() {
	awk '($3 == fs || fs == "") && ( $2 == mp || mp == "") && \
		( $1 == dev || dev == "" ) { e=0; };  END { exit(e); }' \
		e=1 "fs=$1" "dev=$2" "mp=$3" /proc/self/mounts
}

mount_once() {
	local fs="$1" dev="$2" mp="$3"
	is_mounted "$fs" "$dev" "$mp" && return
	shift 3
	mount -t "$fs" "$dev" "$mp" "$@"
}

is_lxc() {
	local container=""

	if [ -n "$_LXC_CONTAINER" ]; then
		[ "${_LXC_CONTAINER}" != "none" ]
		return
	fi

	[ "$0" = "/init" ] && return 1 # lxc wont use /init (not a ramdisk)

	if [ -f /proc/1/environ ]; then
		local x=""
		x=$(awk -F'\0' '{
			for(i=1;i<=NF;i++) {
				if ($i ~ /container=./ || $i ~ /LIBVIRT_LXC_UUID=./)
					found=$i;
			}
            }
			END { sub(/container=/,"", found); printf("%s\n",found); }' \
			/proc/1/environ)
		[ -n "$x" ] && container="$x"
	fi

	# Detect OpenVZ containers
	if [ -z "$container" ]; then
		[ -d /proc/vz ] && [ ! -d /proc/bc ] && container=openvz
	fi

	# Detect Vserver containers
	if [ -z "$container" -a -f /proc/self/status ]; then
		while read k v; do
			[ "$k" = "VxID:" ] && [ ${v:-0} -gt 1 ] &&
				container=vserver && break;
		done < /proc/self/status >/dev/null 2>&1
	fi

	_LXC_CONTAINER="${container:-none}"
	[ -n "$container" ]
	return
}
is_not_lxc() { ! is_lxc; }

iamroot() {
	[ -n "$UID" ] || UID=$(id -u) || error "unable to determine UID!"
	[ "$UID" = "0" ]
}
asroot() {
	local sudo=""
	iamroot || sudo="sudo"
	$sudo "$@"
}

path_has() {
	# path_has(tok, [string=$PATH], [delim=:])
	local tok="${1%/}" del="${3:-:}" p="${2:-${PATH}}"
	p="${del}$p${del}"
	[ "${p#*${del}${tok}${del}}" != "$p" ] ||
		[ "${p#*${del}${tok}/${del}}" != "$p" ]
}

replace() {
	# replace(src,from,to,max=-1)
	# replace char 'from' in 'src' with 'to'.
	# For a few replacements, this is much faster than
	# using sed or tr as it does not invoke any subprocesses.
	local src="$1" from="$2" to="$3" max="${4:--1}"
	# if no replaces were to be done return src
	[ $max -eq 0 ] && { _RET="$src"; return 0; }

	local pre="" post="" rem="" ret="" count=0
	rem="$src"
	while [ -n "$rem" ] && [ $max -lt 0 -o $count -lt $max ]; do
		if [ "$count" = "$max" ]; then
			ret="$ret$rem"
			break
		fi
		post=${rem#*${from}}
		if [ "$post" = "$rem" ]; then
			ret="$ret$rem"
			rem=""
		else
			pre=${rem%${from}${post}}
			ret="$ret$pre$to"
			rem="$post"
		fi
		count=$(($count+1))
	done
	_RET="$ret$rem"
}

# vi: ts=4 noexpandtab syntax=sh
